﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.ComponentModel;

using Optimization.Utilities;

namespace Optimization.StandardSolvers
{
    /*
    [DescriptionAttribute("Expand to see the spelling options for the application.")]
    public class TestProp
    {
        private int t1;
        [DescriptionAttribute("...")]
        public int T1
        {
            get { return t1; }
            set { t1 = value; }
        }

        private int t2;
        [DescriptionAttribute("...")]
        public int T2
        {
            get { return t2; }
            set { t2 = value; }
        }

    }
    */
    [IsSolver]
    public class PSO : ParticleBasedMethod, ISolver
    {
        
        public class PSOParticle : Particle
        {
            public Vector BestSolution;
            public double BestSolutionValue;

            public PSOParticle(Vector solution, double value)
            {
                this.Position = solution;
                this.Value = value;

                BestSolution = new Vector(solution);
                BestSolutionValue = value;

                Velocity = Vector.Zero(this.Position.Length);
            }
        }

        protected System.Random rnd = new Optimization.Utilities.Random();

        static public int DefaultSwarmSize = 10;
        static public float DefaultC0 = 0.721f;
        static public float DefaultC1 = 1.193f;
        static public float DefaultC2 = 1.193f;
        static public float DefaultC3 = 0.5f;
        /*      double cc0 = rnd.NextDouble() * 0.721;
                double cc1 = rnd.NextDouble() * 1.193;
                double cc2 = rnd.NextDouble() * 1.193;
         */
        static public float DefaultStepSize = 0.1f;

        static public double RepulsiveCoef = 0.1;


        private float c0 = 0.1f;
        private float c1 = 0.1f;
        private float c2 = 0.1f;
        private float c3 = 0.5f;

        private int swarmSize = 10;

        private float stepSize = 1f;

        public PSO()
        {
            swarmSize = DefaultSwarmSize;
            c0 = DefaultC0;
            c1 = DefaultC1;
            c2 = DefaultC2;
            c3 = DefaultC3;
            stepSize = DefaultStepSize;

        }


        /*
        private TestProp test;
        [CategoryAttribute("Algorithm Settings"), DescriptionAttribute("...")]
        [TypeConverter(typeof(ExpandableObjectConverter))]
        public TestProp Test
        {
            get { return test; }
            set { test = value; }
        }
        */

        [CategoryAttribute("Algorithm Settings"), DescriptionAttribute("SwarmSize")]
        public int SwarmSize
        {
            get { return swarmSize; }
            set 
            {
                if (this.Count() != value)
                {
                    swarmSize = value; 
                    InitSwarm();
                }
            }
        }

        [CategoryAttribute("PSO Settings"), DescriptionAttribute("C0")]
        public float C0
        {
            get { return c0; }
            set { c0 = value; }
        }

        [CategoryAttribute("PSO Settings"), DescriptionAttribute("C1")]
        public float C1
        {
            get { return c1; }
            set { c1 = value; }
        }

        [CategoryAttribute("PSO Settings"), DescriptionAttribute("C2")]
        public float C2
        {
            get { return c2; }
            set { c2 = value; }
        }

        [CategoryAttribute("PSO Settings"), DescriptionAttribute("C3")]
        public float C3
        {
            get { return c3; }
            set { c3 = value; }
        }

        [CategoryAttribute("Algorithm Settings"), DescriptionAttribute("Size of one step used in computation")]
        public float StepSize
        {
            get { return stepSize; }
            set { stepSize = value; }
        }

        public void DefineProblem(Func<IProblem> cf)
        {

            ProblemFactory = cf;
            Problem = ProblemFactory();

            if (Problem == null)
                throw new Exception("ClassFactory for the problem did not create instance with IProblem interface");

            Solution = Problem.RandomSolution();
            SolutionValue = Problem.Evaluate(Solution);

            InitSwarm();
        }

        virtual public void InitSwarm()
        {
            if (Problem == null)
                return;

            this.Clear();
            for (int i = 0; i < swarmSize; i++)
            {
                Vector rnd = Problem.RandomSolution();
                double value = Problem.Evaluate(rnd);
                this.Add(new PSOParticle(rnd, value));
            }
        }

        public Vector GetSolution()
        {
            return new Vector(Solution);
        }

        public void SetSolution(Vector solution, double solutionValue)
        {
            if (solutionValue < SolutionValue)
            {
                externalSolution = new Vector(solution);
                externalSolutionValue = solutionValue;
            }
        }

        public double GetSolutionValue()
        {
            return SolutionValue;
        }

        /// <summary>
        /// Selects the best value from particles and if this solution is better than current it is memorized
        /// </summary>
        void UpdateBestSolution()
        {
            Vector swarmBestSolution = this[0].Position;
            double swarmBestSolutionValue = this[0].Value;

            PSOParticle bestParticle = this[0] as PSOParticle;
            foreach (Particle p in this)
            {
                PSOParticle particlePSO = p as PSOParticle;
                // Update best personal solution considering current position
                if (particlePSO.Value < particlePSO.BestSolutionValue)
                {
                    particlePSO.BestSolutionValue = particlePSO.Value;
                    particlePSO.BestSolution = new Vector(particlePSO.Position);
                }

                // Update swarm Solution considering current particle
                if (particlePSO.BestSolutionValue < swarmBestSolutionValue)
                {
                    bestParticle = particlePSO;
                    swarmBestSolutionValue = particlePSO.BestSolutionValue;
                    swarmBestSolution = new Vector(particlePSO.BestSolution);
                }
            }

            if (bestParticle != null)
            {
                if (SolutionValue > swarmBestSolutionValue)
                {
                    Solution = new Vector(swarmBestSolution);
                    SolutionValue = swarmBestSolutionValue;
                }

                this.Remove(bestParticle);
                this.Insert(0, bestParticle);
            }

            //erase external solution if PSO found better
            if (externalSolutionValue >= SolutionValue)
                externalSolution = null;
        }

        /// <summary>
        /// According to PSO method computes velocities for all particles
        /// source : http://cs.wikipedia.org/wiki/Optimalizace_hejnem_%C4%8D%C3%A1stic
        /// 
        /// </summary>
        protected virtual void UpdateVelocities()
        {
            foreach (Particle p in this)
            {
                PSOParticle psoP = p as PSOParticle;
                double cc0 = rnd.NextDouble() * C0;
                double cc1 = rnd.NextDouble() * C1;
                double cc2 = rnd.NextDouble() * C2;
                double cc3 = rnd.NextDouble() * C3;
                psoP.Velocity = cc0 * psoP.Velocity + cc1 * (psoP.BestSolution - psoP.Position);

                if (true)
                    psoP.Velocity += cc2 * (Solution - psoP.Position);

                //change algortihm to be able accomodate external solution
                if (externalSolution != null)
                    psoP.Velocity = psoP.Velocity + cc3 * (externalSolution - psoP.Position);

            }
        }

        /// <summary>
        /// Moves the particles. This method have to be called after method UpdateVelocities()
        /// </summary>
        /// <param name="stepSize"></param>
        void MoveParticles(double stepSize)
        {
            foreach (Particle p in this)
                p.Position = p.Position + stepSize * p.Velocity;

            //*
            foreach (Particle p in this)
                p.Value = Problem.Evaluate(p.Position);
            //*/
            /*
            System.Threading.Tasks.Parallel.ForEach(this,
                p =>
                {
                    IProblem lp = ProblemFactory();
                    p.Value = lp.Evaluate(p.Position);

                });
            //*/
        }

        public void Step()
        {
            UpdateVelocities();
            MoveParticles(stepSize);
            UpdateBestSolution();
        }
    }

    [IsSolver]
    public class PSO_D : PSO
    {
        protected override void UpdateVelocities()
        {
            foreach (Particle p in this)
            {
                PSOParticle psoP = p as PSOParticle;
                double cc0 = C0;
                
                double cc1 = rnd.NextDouble() * C1;
                double cc2 = rnd.NextDouble() * C2;
                double cc3 = rnd.NextDouble() * C3;

                //double cc1 = C1;
                //double cc2 = C2;
                //double cc3 = C3;

                psoP.Velocity = cc0 * psoP.Velocity + cc1 * (psoP.BestSolution - psoP.Position);

                if (true)
                    psoP.Velocity += cc2 * (Solution - psoP.Position);

                //change algorithm to be able accomodate external solution
                if (externalSolution != null)
                    psoP.Velocity = psoP.Velocity + cc3 * (externalSolution - psoP.Position);

            }
        }
    }
}
